﻿using System;
using System.Linq;

namespace Notoric.ViewModel.Elements.CWNotation
{
    /// <summary>
    /// Adapts a <see cref="Model.Elements.CWNotation.BarlineSegment"/> for a Windows Presentation Foundation view.
    /// </summary>
    /// <remarks>
    /// This class is not exactly an adapter, since, in the model, a <see cref="Model.Elements.CWNotation.BarlineSegment"/>
    /// exists only inside the <see cref="Model.Elements.PartDescriptorElement"/> not a <see cref="Model.Elements.CWNotation.Barline"/>
    /// and has a descriptive role (in all the systems, every barline is drawn in these segments on this part).
    /// Whilst this <see cref="BarlineSegment"/> is owned by a <see cref="Barline"/> instance and corresponds directly to a visual line
    /// that will be drawn by the view.
    /// </remarks>
    public class BarlineSegment : ViewModelElement
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="BarlineSegment"/> instance.
        /// </summary>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="Barline"/>
        /// </para>
        /// <para>
        /// The barline that directly owns this segment.
        /// </para>
        /// </param>
        /// <param name="adaptedSegment">
        /// <para>
        /// Type: <see cref="Model.Elements.CWNotation.BarlineSegment"/>
        /// </para>
        /// <para>
        /// The adapted barline segment.
        /// </para>
        /// </param>
        /// <exception cref="NullReferenceException">
        /// <paramref name="owner"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="adaptedSegment"/> is <see langword="null"/>.
        /// </exception>
        public BarlineSegment(Barline owner, Model.Elements.CWNotation.BarlineSegment adaptedSegment)
            : base(adaptedSegment, owner, owner.UndoStack, owner.RedoStack)
        {
            var system = FindOwner<System>();

            lowerStaff = system.Staves.FirstOrDefault(s => ((Model.Elements.CWNotation.Staff)s.AdaptedElement).Descriptor == adaptedSegment.LowerStaff);
            upperStaff = system.Staves.FirstOrDefault(s => ((Model.Elements.CWNotation.Staff)s.AdaptedElement).Descriptor == adaptedSegment.UpperStaff);

            lowerStaff.PropertyChanged += new global::System.ComponentModel.PropertyChangedEventHandler(lowerStaff_PropertyChanged);
            upperStaff.PropertyChanged += new global::System.ComponentModel.PropertyChangedEventHandler(upperStaff_PropertyChanged);

            owner.PropertyChanged += new global::System.ComponentModel.PropertyChangedEventHandler(owner_PropertyChanged);
        }

        #endregion

        #region ViewModelElement Members

        /// <inheritdoc/>
        public override void Dispose()
        {
            lowerStaff.PropertyChanged -= lowerStaff_PropertyChanged;
            upperStaff.PropertyChanged -= upperStaff_PropertyChanged;
            base.Dispose();
        }

        /// <inheritdoc/>
        public override string ElementNameKey
        {
            get { return FindOwner<Barline>().ElementNameKey; }
        }

        #endregion

        #region Event Handlers

        /// <inheritdoc/>
        protected override bool AdaptedElement_PropertyChanged(global::System.ComponentModel.PropertyChangedEventArgs e)
        {
            var adaptedSegment = (Model.Elements.CWNotation.BarlineSegment)AdaptedElement;

            if (e.PropertyName == "LowerStaff")
            {
                lowerStaff.PropertyChanged -= lowerStaff_PropertyChanged;
                lowerStaff = FindOwner<System>().Staves.FirstOrDefault(s => ((Model.Elements.CWNotation.Staff)s.AdaptedElement).Descriptor == adaptedSegment.LowerStaff);
                lowerStaff.PropertyChanged += new global::System.ComponentModel.PropertyChangedEventHandler(lowerStaff_PropertyChanged);
                RaisePropertyChanged("Y2");
            }
            else if (e.PropertyName == "UpperStaff")
            {
                upperStaff.PropertyChanged -= upperStaff_PropertyChanged;
                upperStaff = FindOwner<System>().Staves.FirstOrDefault(s => ((Model.Elements.CWNotation.Staff)s.AdaptedElement).Descriptor == adaptedSegment.UpperStaff);
                upperStaff.PropertyChanged += new global::System.ComponentModel.PropertyChangedEventHandler(upperStaff_PropertyChanged);
                RaisePropertyChanged("Y1");
            }

            return true;
        }

        /// <summary>
        /// Occurs when the value of some property of the <see cref="ViewModelElement.Owner"/> has changed.
        /// </summary>
        /// <remarks>
        /// <b>Operation:</b><br/>
        /// If the modified property is "Position" the handler raises the <see cref="ViewModelElement.PropertyChanged"/> event for the <see cref="X"/> property.
        /// </remarks>
        /// <param name="sender">Object that has sent the event</param>
        /// <param name="e">Event data</param>
        private void owner_PropertyChanged(object sender, global::System.ComponentModel.PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Position")
                RaisePropertyChanged("X");
        }

        /// <summary>
        /// Occurs when the value of some property of the <see cref="UpperStaff"/> has changed.
        /// </summary>
        /// <remarks>
        /// <b>Operation:</b><br/>
        /// If the modified property is "Top" the handler raises the <see cref="ViewModelElement.PropertyChanged"/> event for the <see cref="Y1"/> property.
        /// </remarks>
        /// <param name="sender">Object that has sent the event</param>
        /// <param name="e">Event data</param>
        void upperStaff_PropertyChanged(object sender, global::System.ComponentModel.PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Top")
                RaisePropertyChanged("Y1");
        }

        /// <summary>
        /// Occurs when the value of some property of the <see cref="LowerStaff"/> has changed.
        /// </summary>
        /// <remarks>
        /// <b>Operation:</b><br/>
        /// If the modified property is "Top" the handler raises the <see cref="ViewModelElement.PropertyChanged"/> event for the <see cref="Y2"/> property.
        /// </remarks>
        /// <param name="sender">Object that has sent the event</param>
        /// <param name="e">Event data</param>
        void lowerStaff_PropertyChanged(object sender, global::System.ComponentModel.PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Top")
                RaisePropertyChanged("Y2");
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the lower staff.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Staff"/>
        /// </para>
        /// <para>
        /// The lower staf of this segment.
        /// </para>
        /// </value>
        public Staff LowerStaff
        {
            get { return lowerStaff; }
        }

        /// <summary>
        /// Gets the upper staff.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Staff"/>
        /// </para>
        /// <para>
        /// The upper staf of this segment.
        /// </para>
        /// </value>
        public Staff UpperStaff
        {
            get { return upperStaff; }
        }

        /// <summary>
        /// Gets or sets the vertical coordinate of the line end point.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Double"/>
        /// </para>
        /// <para>
        /// The vertical coordinate (in device independent pixels) of the line end point.
        /// </para>
        /// </value>
        public double Y2
        {
            get { return lowerStaff.Top + lowerStaff.Height; }
        }

        /// <summary>
        /// Gets or sets the horizontal coordinate of the line.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Double"/>
        /// </para>
        /// <para>
        /// The horizontal coordinate (in device independent pixels) of the line.
        /// </para>
        /// </value>
        public double X
        {
            get { return FindOwner<Barline>().Position; }
        }

        /// <summary>
        /// Gets or sets the vertical coordinate of the line start point.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Double"/>
        /// </para>
        /// <para>
        /// The vertical coordinate (in device independent pixels) of the line start point.
        /// </para>
        /// </value>
        public double Y1
        {
            get { return upperStaff.Top; }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the value of the <see cref="LowerStaff"/> property.
        /// </summary>
        private Staff lowerStaff;
        /// <summary>
        /// Holds the value of the <see cref="UpperStaff"/> property.
        /// </summary>
        private Staff upperStaff;

        #endregion
    }
}
